#ifndef __NOTECOUNTER_CONTROLLER__
#define __NOTECOUNTER_CONTROLLER__

#include <QObject>
#include <QThread>
#include <QSerialPort>
#include <QMutex>

#include "common.h"

#define CNTR_MODE_SELECT_CMD			0x02
#define CNTR_STATUS_CMD					0x03
#define CNTR_STATE_CMD					0x11
#define CNTR_NOTE_INFO_CMD				0x12
#define CNTR_COMM_TEST_CMD				0x14
#define CNTR_FAULT_CLEAR_CMD			0x16
#define CNTR_EXIT_WORK_MODE_CMD			0x20
#define CNTR_ENTER_WORK_MODE_CMD		0x21
#define CNTR_START_COUNT_CMD			0x22
#define CNTR_PAUSE_COUNT_CMD			0x23
#define CNTR_STORE_NOTE_CMD				0x24
#define CNTR_COMPLETE_STORE_NOTE_CMD	0x25
#define CNTR_CLEAR_TRANSACTION_CMD		0x26
#define CNTR_OPEN_SHELL_CMD				0x31
#define CNTR_CLOSE_SHELL_CMD			0x32
#define CNTR_FETCH_EJECT_INFO_CMD		0x33
#define CNTR_ENTER_COIN_MODE_CMD		0x41
#define CNTR_EXIT_COIN_MODE_CMD			0x42

#define CNTR_COUNT_INFO_RESPONSE1		0x12
#define CNTR_COUNT_INFO_RESPONSE2		0x13
#define CNTR_FAULT_REPORT_CMD_RESPONSE	0x15
#define CNTR_ONLINE_CHECK_RESPONSE		0x17
#define CNTR_NOTESERIAL_REPORT_RESPONSE	0x18
#define CNTR_STORE_IN_PROGRESS_RESPONSE	0x24
#define CNTR_COMPLETE_STORE_RESPONSE	0x25
#define CNTR_EJECT_INFO_RESPONSE		0x33

#define CNTR_STATUS_OK					0x06
#define CNTR_STATUS_MODE_ERROR			0x15
#define CNTR_STATUS_NOTE_EXIST1_ERROR	0x16
#define CNTR_STATUS_NOTE_EXIST2_ERROR	0x17
#define CNTR_STATUS_NOTE_EXIST3_ERROR	0x18
#define CNTR_STATUS_DEBUG				0x15
#define CNTR_STATUS_FAULT				0x16
#define CNTR_STATUS_COIN				0x17

#define CNTR_FAULT_PS01_LEFT			0x0001
#define CNTR_FAULT_PS01_RIGHT			0x0002
#define CNTR_FAULT_PS02_LEFT			0x0004
#define CNTR_FAULT_PS02_RIGHT			0x0008
#define CNTR_FAULT_PS03_LEFT1			0x0010
#define CNTR_FAULT_PS03_LEFT2			0x0020
#define CNTR_FAULT_PS03_RIGHT2			0x0040
#define CNTR_FAULT_PS03_RIGHT1			0x0080
#define CNTR_FAULT_PS04					0x0100
#define CNTR_FAULT_PS06					0x0200
#define CNTR_FAULT_STORE_MOTOR			0x0400
#define CNTR_FAULT_STORE_GATE_OPEN		0x0800
#define CNTR_FAULT_PS08_LEFT			0x1000
#define CNTR_FAULT_PS08_RIGHT			0x2000
#define CNTR_FAULT_COUNT1				0x4000
#define CNTR_FAULT_COUNT2				0x8000

#define CNTR_FAULT_UPPER_PART			0x0001
#define CNTR_FAULT_SHELL_OPEN			0x0002
#define CNTR_FAULT_SHELL_CLOSE			0x0004
#define CNTR_FAULT_STORE_GATE_CLOSE		0x0008
#define CNTR_FAULT_PS09_RIGHT2			0x0040
#define CNTR_FAULT_PS09_RIGHT1			0x0080
#define CNTR_FAULT_NOTE_ENTRY			0x0100
#define CNTR_FAULT_PS05_MIDDLE			0x0200
#define CNTR_FAULT_PS05_LEFT			0x0400
#define CNTR_FAULT_PS05_RIGHT			0x0800
#define CNTR_FAULT_MAGNETIC_SIGNAL		0x1000
#define CNTR_FAULT_IMAGE_SENSOR_COMM1	0x2000
#define CNTR_FAULT_IMAGE_SENDOR_COMM2	0x4000
#define CNTR_FAULT_STORE_NOTE			0x8000

#define CNTR_STATUS_SUCCEED				0x06
#define CNTR_STATUS_COLLECT_NOT_EMPTY	0x16
#define CNTR_STATUS_REJECT_NOT_EMPTY	0x17
#define CNTR_STATUS_ENTRY_NOT_EMPTY		0x18
#define CNTR_STATUS_ENTRY_NO_NOTE		0x16
#define CNTR_STATUS_IN_WORKING			0x15
#define CNTR_STATUS_NOTE_NOT_CLEAR		0x16
#define CNTR_STATUS_COLLECT_NOT_EMPTY1	0x17
#define CNTR_STATUS_COLLECT_EMPTY		0x16
#define CNTR_STATUS_STORE_FAIL			0x16

class Coordinator;
class QTimer;

class NCController_Worker : public QObject
{
	Q_OBJECT

public:

	struct SerialConfig {
		QString port;
		uint32_t baud = QSerialPort::Baud115200;
		QSerialPort::DataBits data_bits = QSerialPort::Data8;
		QSerialPort::StopBits stop_bits = QSerialPort::OneStop;
		QSerialPort::Parity parity = QSerialPort::NoParity;
	};
	typedef struct SerialConfig SerialConfig;

    NCController_Worker(QObject *parent = nullptr);
    ~NCController_Worker();

    void setSerialParam(SerialConfig param);

    enum {
    	MODE_INIT,
    	MODE_INTELLIGENT,
    	MODE_MIXED,
    	MODE_COUNT,
    	MODE_NOTE_TRANSACTION,
    	MODE_COIN_TRANSACTION
    };
    enum {
    	SHELL_CMD_CLOSE = -1,
    	SHELL_CMD_NONE,
    	SHELL_CMD_OPEN
    };

    bool online() { return m_online; }

    uint32_t transaction_mode() { return m_transaction_mode; };
    bool shell_opened() { return !m_shell_closed/* && m_shell_cmd_status != SHELL_CMD_OPEN*/; };
    bool entry_empty() { return m_entry_empty; };
    bool reject_empty() { return m_reject_empty; };
    bool collect_empty() { return m_collect_empty; };

	void status_to_text(uint8_t cmd, uint8_t status, QString &cmd_name, QString &status_name);

signals:

	void nc_status(uint16_t status);
	void transaction_count_info(NoteInfo note_info);
	void count_exception(uint16_t cmd, uint16_t exception_code, QString exception_name);
	void count_started();
	void count_complete(uint16_t status);
	void store_in_progress();
	void store_exception(uint16_t cmd, uint16_t exception_code, QString exception_name);
	void store_complete(uint16_t status);
	void nc_error(uint16_t error1, uint16_t error2);
	void paper_action(uint8_t act);

public slots:

	void start_work();
	void stop_work();

	void set_mode(uint32_t mode);
	void start_count();
	void start_store();
	void complete_store();
	void start_coin_transaction();
	void complete_coin_transaction();
	void clear_transaction();

	void open_shell();
	void close_shell();

	void inquiry_nc_status();

private slots:

	void process_cmd_resp();

private:

	void transfer_cmd(QByteArray cmd);

    QByteArray rcsv_analyze(QByteArray &raw_data);
	void parse_msg(QByteArray msg);

	void check_online();
	void clear_faults();
	void start_transaction();
	void complete_transaction();

	QByteArray parse_response(QByteArray resp);
	QByteArray pack_command(uint32_t cmd, uint32_t para = 0);

	void cmd_status(uint8_t cmd);
	void response_status(uint8_t cmd, uint8_t status);

private:

	QMutex m_mutex;

	SerialConfig m_param;
	QSerialPort *m_port;
	QTimer *m_worker_timer;

	QList<QByteArray> m_cmd_list;
	QByteArray m_current_cmd;
	uint16_t m_current_cmd_fail_cnt;
	QByteArray m_response;
	uint16_t m_cmd_status;
	uint16_t m_wait_time;

	uint32_t m_transaction_mode;
	bool m_shell_closed;
	bool m_entry_empty;
	bool m_reject_empty;
	bool m_collect_empty;

	int m_shell_cmd_status;			// init: 0, open: 1, close: -1;

	bool m_online;
};

class NCController : public QObject
{
	Q_OBJECT

public:

    NCController(QObject *parent = nullptr);
    ~NCController();

    NCController_Worker *getWorker() {
        return m_worker;
    }

signals:

	void start();
	void stop();

private:

	QThread m_worker_thread;
	NCController_Worker *m_worker;

    Coordinator *m_coordinator;
};

#endif
